/*
 *  stk3a5x_calibration.c - Linux kernel modules for sensortek stk3a5x,
 *  proximity/ambient light sensor (factory calibration)
 *
 *  Copyright (C) 2012~2019 Taka Chiu, sensortek Inc.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include "stk3a5x.h"

#if (defined(STK_ALS_CALI) || defined(STK_PS_CALI))
static void stk3a5x_cali_func(struct work_struct *work);
static int32_t stk3a5x_update_registry(struct stk3a5x_data *alps_data);

static enum hrtimer_restart stk_cali_timer_func(struct hrtimer *timer)
{
    struct stk3a5x_data *alps_data = container_of(timer, struct stk3a5x_data, cali_timer);
    queue_work(alps_data->stk_cali_wq, &alps_data->stk_cali_work);
    hrtimer_forward_now(&alps_data->cali_timer, alps_data->cali_poll_delay);
    return HRTIMER_RESTART;
}

static int32_t stk3a5x_register_cali_timer(struct stk3a5x_data *alps_data, stk3a5x_cali_timer_type timer_type)
{
    printk(KERN_ERR "%s: regist 0x%X timer\n", __func__, timer_type);

    if (alps_data->cali_info.timer_status.timer_is_active)
    {
        printk(KERN_ERR "%s: ALS: Other calibration is running\n", __func__);
        return 0;
    }

    if (!alps_data->cali_info.timer_status.timer_is_exist)
    {
        printk(KERN_ERR "%s: ALS: regist cali timer\n", __func__);
        alps_data->stk_cali_wq = create_singlethread_workqueue("stk_cali_wq");
        INIT_WORK(&alps_data->stk_cali_work, stk3a5x_cali_func);
        hrtimer_init(&alps_data->cali_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
        alps_data->cali_poll_delay = ns_to_ktime(55 * NSEC_PER_MSEC);
        alps_data->cali_timer.function = stk_cali_timer_func;
        alps_data->cali_info.timer_status.timer_is_exist = true;
    }

    switch (timer_type)
    {
        case STK3A5X_CALI_TIMER_ALS: // ALS calibration timer
            alps_data->cali_info.cali_sensor = STK3A5X_CALI_TIMER_ALS;
            alps_data->cali_info.cali_status = STK3A5X_CALI_RUNNING;
            break;

        case STK3A5X_CALI_TIMER_PS: // PS calibration timer
            alps_data->cali_info.cali_sensor = STK3A5X_CALI_TIMER_PS;
            alps_data->cali_info.cali_status = STK3A5X_CALI_RUNNING;
            break;
    }

    if (!alps_data->cali_info.timer_status.timer_is_active)
    {
        printk(KERN_ERR "%s: Start cali timer\n", __func__);
        hrtimer_start(&alps_data->cali_timer, alps_data->cali_poll_delay, HRTIMER_MODE_REL);
        alps_data->cali_info.timer_status.timer_is_active = true;
    }

    printk(KERN_ERR "%s: Regist cali timer done\n", __func__);
    return 0;
}

static int32_t stk3a5x_factory_cali_ctrl_setting(uint8_t* reg_config, stk3a5x_sensor_type sensor_type)
{
    uint8_t CTRL_TYPE = 0xFF;
    uint16_t err = 0;
    printk(KERN_ERR "%s: start\n", __func__);

    switch (sensor_type)
    {
        case STK3A5X_ALS:
            CTRL_TYPE = STK3A5X_ALSCTRL1_REG;
            break;

        case STK3A5X_PS:
            CTRL_TYPE = STK3A5X_PSCTRL_REG;
            break;

        default:
            printk(KERN_ERR "%s: Unknown type\n", __func__);
            break;
    }

    if (CTRL_TYPE == 0xFF)
    {
        return -1;
    }

    printk(KERN_ERR "%s: get reg setting\n", __func__);
    stk3a5x_get_reg_default_setting(CTRL_TYPE, &err);
    printk(KERN_ERR "%s: get reg setting result = 0x%X\n", __func__, err);

    if (err > 0xFF)
    {
        return -1;
    }
    else
    {
        *reg_config = (err & 0xFF);
        return 0;
    }
}
static int32_t stk3a5x_als_get_data_without_notify(struct stk3a5x_data *alps_data, uint16_t* als_data)
{
    int32_t rv = 0;
    uint8_t  raw_data[10];
    uint16_t als_raw_data[5];
    int      loop_count;
    rv = STK3A5X_REG_BLOCK_READ(alps_data, STK3A5X_DATA1_ALS_REG, 10, &raw_data[0]);

    if (rv < 0)
    {
        printk(KERN_ERR "%s: read i2c (0x%X) error\n", __func__, STK3A5X_DATA1_ALS_REG);
        return rv;
    }

    for (loop_count = 0; loop_count < (sizeof(als_raw_data) / sizeof(als_raw_data[0])); loop_count++)
    {
        *(als_raw_data + loop_count ) = (*(raw_data + (2 * loop_count)) << 8 | *(raw_data + (2 * loop_count + 1) ));
    }

    *als_data = als_raw_data[0];
    return rv;
}

static int32_t stk3a5x_als_factory_cali_enable(struct stk3a5x_data *alps_data, bool en)
{
    uint8_t als_reg_config;
    bool config_changed = false;
    int32_t rv = 0;
    printk(KERN_ERR "%s: en = %d\n", __func__, en);

    if (en)
    {
        rv = stk3a5x_factory_cali_ctrl_setting(&als_reg_config, STK3A5X_ALS);

        if (rv < 0)
        {
            return rv;
        }

        rv = STK3A5X_REG_READ_MODIFY_WRITE(alps_data, STK3A5X_ALSCTRL1_REG, als_reg_config, 0xFF);

        if (rv < 0)
        {
            printk(KERN_ERR "%s: read modify write i2c (0x%X) error\n", __func__, STK3A5X_ALSCTRL1_REG);
            return rv;
        }
    }

    rv = STK3A5X_REG_READ(alps_data, STK3A5X_STATE_REG);

    if (rv < 0)
    {
        return rv;
    }

    als_reg_config = (uint8_t)rv;
    als_reg_config &= (~(STK3A5X_STATE_EN_PS_MASK | STK3A5X_STATE_EN_ALS_MASK | STK3A5X_STATE_EN_WAIT_MASK));

    if (en)
    {
        if (alps_data->als_info.enable)
        {
            printk(KERN_ERR "%s: ALS is running, ignore!\n", __func__);
        }
        else
        {
            if (alps_data->ps_info.enable
#ifdef STK_TUNE0
                || alps_data->ps_info.boot_enable
#endif
#ifdef STK_PS_CALI
                || alps_data->ps_info.cali_enable
#endif
               )
            {
                als_reg_config |= (STK3A5X_STATE_EN_PS_MASK | STK3A5X_STATE_EN_ALS_MASK);
            }
            else
            {
                als_reg_config |= STK3A5X_STATE_EN_ALS_MASK;
            }

            config_changed = true;
        }
    }
    else
    {
        if (alps_data->als_info.enable)
        {
            printk(KERN_ERR "%s: Other thread used, ignore!\n", __func__);
        }
        else
        {
            if (alps_data->ps_info.enable
#ifdef STK_TUNE0
                || alps_data->ps_info.boot_enable
#endif
#ifdef STK_PS_CALI
                || alps_data->ps_info.cali_enable
#endif
               )
            {
                als_reg_config |= (STK3A5X_STATE_EN_PS_MASK | STK3A5X_STATE_EN_WAIT_MASK);
            }

            config_changed = true;
        }
    }

    if (config_changed)
    {
        rv = STK3A5X_REG_READ_MODIFY_WRITE(alps_data, STK3A5X_STATE_REG, als_reg_config, 0xFF);

        if (rv < 0)
        {
            printk(KERN_ERR "%s: read modify write i2c (0x%X) error\n", __func__, STK3A5X_STATE_REG);
            return rv;
        }
    }

    printk(KERN_ERR "%s: cali enable done rv = 0x%x\n", __func__, rv);
    alps_data->als_info.cali_enable = en;
    return rv;
}

int32_t stk3a5x_cali_als(struct stk3a5x_data *alps_data)
{
    int32_t ret = 0;

    switch (alps_data->cali_info.cali_status)
    {
        case STK3A5X_CALI_IDLE:
            printk(KERN_ERR "%s: idle\n", __func__);
            ret = stk3a5x_als_factory_cali_enable(alps_data, true);

            if (ret < 0)
            {
                printk(KERN_ERR "%s: cali enable failed\n", __func__);
                return ret;
            }

            printk(KERN_ERR "%s: cali enable Success\n", __func__);
            ret = stk3a5x_register_cali_timer(alps_data, STK3A5X_CALI_TIMER_ALS);
            printk(KERN_ERR "%s: cali enable Success - 01\n", __func__);

            if (ret < 0)
            {
                printk(KERN_ERR "%s: regist timer failed\n", __func__);
                return ret;
            }

            break;

        case STK3A5X_CALI_RUNNING:
        case STK3A5X_CALI_FAILED:
            printk(KERN_ERR "%s: STK3A5X_CALI_RUNNING: running or failed\n", __func__);
            alps_data->als_info.als_cali_data = 0;
            ret = stk3a5x_als_get_data_without_notify(alps_data, &alps_data->als_info.als_cali_data);

            if (ret < 0)
            {
                printk(KERN_ERR "%s: STK3A5X_CALI_RUNNING: get data failed\n", __func__);
                return ret;
            }

            if (alps_data->als_info.als_cali_data > 0)
            {
                alps_data->als_info.scale = (uint32_t)(STK3A5X_ALS_CALI_TARGET_LUX * 1000) / alps_data->als_info.als_cali_data;
                alps_data->cali_info.cali_para.als_version ++;
                alps_data->cali_info.cali_para.als_scale = alps_data->als_info.scale;
                alps_data->cali_info.cali_status = STK3A5X_CALI_DONE;
                printk(KERN_ERR "%s: ALS scale = %d\n", __func__, alps_data->als_info.scale);
                ret = stk3a5x_als_factory_cali_enable(alps_data, false);

                if (ret < 0)
                {
                    printk(KERN_ERR "%s: STK3A5X_CALI_RUNNING: cali disable failed\n", __func__);
                    return ret;
                }
            }
            else
            {
                alps_data->cali_info.cali_status = STK3A5X_CALI_FAILED;
                alps_data->als_info.cali_failed_count ++;

                if (alps_data->als_info.cali_failed_count >= 10)
                {
                    alps_data->cali_info.cali_status = STK3A5X_CALI_DONE;
                }
            }

            break;

        case STK3A5X_CALI_DONE:
            printk(KERN_ERR "%s: done\n", __func__);

            if (alps_data->als_info.cali_failed_count < 10)
            {
                stk3a5x_update_registry(alps_data);
                printk(KERN_ERR "%s: STK3A5X_CALI_DONE: Success\n", __func__);
            }
            else
            {
                printk(KERN_ERR "%s: STK3A5X_CALI_DONE: Failed\n", __func__);
            }

            alps_data->cali_info.cali_status = STK3A5X_CALI_IDLE;

            if (alps_data->cali_info.timer_status.timer_is_active)
            {
                alps_data->cali_info.timer_status.timer_is_active = false;
                hrtimer_cancel(&alps_data->cali_timer);
            }

            break;
    }

    printk(KERN_ERR "%s: exit\n", __func__);
    return ret;
}

static int32_t stk3a5x_ps_factory_cali_enable(struct stk3a5x_data *alps_data, bool en)
{
    uint8_t ps_reg_config;
    bool config_changed = false;
    int32_t rv = 0;
    printk(KERN_ERR "%s: en = %d\n", __func__, en);

    if (en)
    {
        rv = stk3a5x_factory_cali_ctrl_setting(&ps_reg_config, STK3A5X_PS);

        if (rv < 0)
        {
            return rv;
        }

        rv = STK3A5X_REG_READ_MODIFY_WRITE(alps_data, STK3A5X_PSCTRL_REG, ps_reg_config, 0xFF);

        if (rv < 0)
        {
            printk(KERN_ERR "%s: read modify write i2c (0x%X) error\n", __func__, STK3A5X_PSCTRL_REG);
            return rv;
        }
    }

    rv = STK3A5X_REG_READ(alps_data, STK3A5X_STATE_REG);

    if (rv < 0)
    {
        return rv;
    }

    ps_reg_config = (uint8_t)rv;
    ps_reg_config &= (~(STK3A5X_STATE_EN_PS_MASK | STK3A5X_STATE_EN_ALS_MASK | STK3A5X_STATE_EN_WAIT_MASK));

    if (en)
    {
        if (alps_data->ps_info.enable
#ifdef STK_TUNE0
            || alps_data->ps_info.boot_enable
#endif
           )
        {
            printk(KERN_ERR "%s: PS is running, ignore!\n", __func__);
        }
        else
        {
            if (alps_data->als_info.enable
#ifdef STK_ALS_CALI
                || alps_data->als_info.cali_enable
#endif
               )
            {
                ps_reg_config |= (STK3A5X_STATE_EN_PS_MASK | STK3A5X_STATE_EN_ALS_MASK);
            }
            else
            {
                ps_reg_config |= (STK3A5X_STATE_EN_PS_MASK | STK3A5X_STATE_EN_WAIT_MASK);
            }

            config_changed = true;
        }
    }
    else
    {
        if (alps_data->ps_info.enable
#ifdef STK_TUNE0
            || alps_data->ps_info.boot_enable
#endif
           )
        {
            printk(KERN_ERR "%s: Other thread used, ignore!\n", __func__);
        }
        else
        {
            if (alps_data->als_info.enable
#ifdef STK_ALS_CALI
                || alps_data->als_info.cali_enable
#endif
               )
            {
                ps_reg_config |= STK3A5X_STATE_EN_ALS_MASK;
            }

            config_changed = true;
        }
    }

    if (config_changed)
    {
        rv = STK3A5X_REG_READ_MODIFY_WRITE(alps_data, STK3A5X_STATE_REG, ps_reg_config, 0xFF);

        if (rv < 0)
        {
            printk(KERN_ERR "%s: read modify write i2c (0x%X) error\n", __func__, STK3A5X_STATE_REG);
            return rv;
        }
    }

    printk(KERN_ERR "%s: cali enable done rv = 0x%x\n", __func__, rv);
    alps_data->ps_info.cali_enable = en;
    return rv;
}

static int32_t stk3a5x_ps_factory_cali_saturation(uint16_t ps_cali_data[])
{
    int16_t diff_ps_data_ct = 0;
    int32_t ret = 0;

    if ( ps_cali_data[0] < STK3A5X_PS_CALI_MAX_CROSSTALK)
    {
        diff_ps_data_ct = ps_cali_data[0] - ps_cali_data[2];

        if (diff_ps_data_ct > STK3A5X_PS_CALI_DIFF)
        {
            printk(KERN_ERR "%s: diff_ps_data_ct = %d\n", __func__, diff_ps_data_ct);
            ret = -1;
        }
    }
    else
    {
        printk(KERN_ERR "%s: MAX_CROSSTALK\n", __func__);
        ret = -2;
    }

    return ret;
}

int32_t stk3a5x_cali_ps(struct stk3a5x_data *alps_data)
{
    int32_t ret = 0;
    uint16_t ps_cali_adc = 0;

    switch (alps_data->cali_info.cali_status)
    {
        case STK3A5X_CALI_IDLE:
            printk(KERN_ERR "%s: idle\n", __func__);
            ret = stk3a5x_ps_factory_cali_enable(alps_data, true);

            if (ret < 0)
            {
                printk(KERN_ERR "%s: cali enable failed\n", __func__);
                return ret;
            }

            alps_data->ps_info.ps_cali_data[0]  = 0;
            alps_data->ps_info.ps_cali_data[1]  = 0;
            alps_data->ps_info.ps_cali_data[2]  = 9999;
            alps_data->ps_info.ps_cali_loop     = 0;
            alps_data->ps_info.ps_cali_data_avg = 0;
            ret = stk3a5x_register_cali_timer(alps_data, STK3A5X_CALI_TIMER_PS);

            if (ret < 0)
            {
                printk(KERN_ERR "%s: regist timer failed\n", __func__);
                return ret;
            }

            break;

        case STK3A5X_CALI_RUNNING:
        case STK3A5X_CALI_FAILED:
            printk(KERN_ERR "%s: STK3A5X_CALI_RUNNING: running or failed\n", __func__);

            if (alps_data->ps_info.cali_failed_count >= 10)
            {
                alps_data->cali_info.cali_status = STK3A5X_CALI_DONE;
                break;
            }

            ret = stk3a5x_ps_get_data_without_notify(alps_data, &ps_cali_adc);

            if ((ps_cali_adc == 0xFFFF) || (ret < 0))
            {
                alps_data->ps_info.cali_failed_count ++;
                return ret;
            }

            alps_data->ps_info.ps_cali_data_avg += ps_cali_adc;

            if (ps_cali_adc > alps_data->ps_info.ps_cali_data[0])
            {
                alps_data->ps_info.ps_cali_data[0] = ps_cali_adc;
            }

            if (ps_cali_adc < alps_data->ps_info.ps_cali_data[2])
            {
                alps_data->ps_info.ps_cali_data[2] = ps_cali_adc;
            }

            alps_data->ps_info.ps_cali_loop ++;

            if (alps_data->ps_info.ps_cali_loop == STK3A5X_PS_CALI_TIMES)
            {
                alps_data->cali_info.cali_status = STK3A5X_CALI_DONE;
            }

            break;

        case STK3A5X_CALI_DONE:
            printk(KERN_ERR "%s: done\n", __func__);
            ret = stk3a5x_ps_factory_cali_enable(alps_data, false);

            if (ret < 0)
            {
                printk(KERN_ERR "%s: cali disable failed\n", __func__);
                return ret;
            }

            if (alps_data->ps_info.cali_failed_count < 10)
            {
                ret = stk3a5x_ps_factory_cali_saturation(alps_data->ps_info.ps_cali_data);

                if (ret < 0)
                {
                    printk(KERN_ERR "%s: cali saturation\n", __func__);
                }
                else
                {
                    alps_data->ps_info.ps_cali_data_avg /= STK3A5X_PS_CALI_TIMES;
                    alps_data->ps_info.ps_cali_data[1]  = (uint16_t)alps_data->ps_info.ps_cali_data_avg;
                    alps_data->ps_info.thd_near         = alps_data->ps_info.ps_cali_data[0] + STK3A5X_HT_N_CT;
                    alps_data->ps_info.thd_far          = alps_data->ps_info.ps_cali_data[0] + STK3A5X_LT_N_CT;
                    alps_data->cali_info.cali_para.ps_version ++;
                    alps_data->cali_info.cali_para.ps_near_thd  = alps_data->ps_info.thd_near;
                    alps_data->cali_info.cali_para.ps_far_thd   = alps_data->ps_info.thd_far;
                    stk3a5x_update_registry(alps_data);
                }
            }
            else
            {
                printk(KERN_ERR "%s: cali failed\n", __func__);
            }

            alps_data->cali_info.cali_status = STK3A5X_CALI_IDLE;

            if (alps_data->cali_info.timer_status.timer_is_active)
            {
                alps_data->cali_info.timer_status.timer_is_active = false;
                hrtimer_cancel(&alps_data->cali_timer);
            }

            break;
    }

    printk(KERN_ERR "%s: exit\n", __func__);
    return ret;
}

static void stk3a5x_cali_func(struct work_struct *work)
{
    struct stk3a5x_data *alps_data = container_of(work, struct stk3a5x_data, stk_cali_work);
    printk(KERN_ERR "%s:start\n", __func__);

    switch (alps_data->cali_info.cali_sensor)
    {
        case STK3A5X_CALI_TIMER_ALS:
            printk(KERN_ERR "%s:ALS\n", __func__);
            stk3a5x_cali_als(alps_data);
            break;

        case STK3A5X_CALI_TIMER_PS:
            printk(KERN_ERR "%s:PS\n", __func__);
            stk3a5x_cali_ps(alps_data);
            break;
    }
}

static int32_t stk3a5x_update_registry(struct stk3a5x_data *alps_data)
{
    int32_t err = 0;
    uint32_t FILE_SIZE = sizeof(stk3a5x_cali_table);
    uint8_t file_buf[FILE_SIZE];
    struct file  *cali_file = NULL;
    mm_segment_t fs;
    loff_t pos;
    printk(KERN_ERR "%s:start\n", __func__);
    memset(file_buf, 0, FILE_SIZE);
    memcpy(file_buf, &alps_data->cali_info.cali_para.als_version, FILE_SIZE);
    cali_file = filp_open(STK3A5X_CALI_FILE, O_CREAT | O_RDWR, 0666);

    if (IS_ERR(cali_file))
    {
        err = PTR_ERR(cali_file);
        printk(KERN_ERR "%s: filp_open error!err=%d,path=%s\n", __func__, err, STK3A5X_CALI_FILE);
        return -1;
    }
    else
    {
        fs = get_fs();
        set_fs(KERNEL_DS);
        pos = 0;
        vfs_write(cali_file, file_buf, sizeof(file_buf), &pos);
        set_fs(fs);
    }

    filp_close(cali_file, NULL);
    printk(KERN_ERR "%s:Done\n", __func__);
    return 0;
}
#endif
